package com.xiyuan.smartutils.template.engine.statement;

import com.xiyuan.smartutils.Lists;
import com.xiyuan.smartutils.StrUtils;
import com.xiyuan.smartutils.Validates;
import com.xiyuan.smartutils.template.engine.StatementParser;
import com.xiyuan.smartutils.template.engine.TagType;
import com.xiyuan.smartutils.template.engine.VariableMap;
import com.xiyuan.smartutils.template.engine.exception.StatementException;
import com.xiyuan.smartutils.template.engine.statement._RETURN.ReturnException;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 函数定义语句且支持嵌套定义, 格式为:<br>
 * <#function name(param1, param2)>
 * <p>
 * </#function>
 *
 * @author lgz 2020年10月21日 新建与整理
 */
public class _FUNCTION extends Statement {
    private static final long serialVersionUID = 1L;
    private static long function_suffix = 0;
    
    public static String getFunctionKey(String functionName) {
        if (function_suffix == 0) {function_suffix = System.nanoTime();}
        return new StringBuilder(functionName).append(function_suffix).toString();
    }
    
    private String functionName;
    private List<String> variableNames = new ArrayList<String>(0);
    
    public _FUNCTION(StatementParser statementParser) {
        super(statementParser);
    }
    
    @Override
    protected void build(VariableMap variableMap, PrintWriter out) {
        if (!variableMap.has(getFunctionKey(functionName))) {variableMap.putValue(getFunctionKey(functionName), this);}
    }
    
    /**
     * 执行函数调用
     **/
    public Object call(VariableMap variableMap, List<Object> args) {
        if (!hasChild()) {return null;}
        
        if (args != null && args.size() > variableNames.size()) {
            throw new StatementException(getPath(),
                                         getLineNo(),
                                         getBeginColumn(),
                                         "调用函数:【<#function " + functionName + "(" + Lists.toString(variableNames,
                                                                                                       ", ") + ")>】参数数目不匹配");
        }
        
        StringWriter writer = new StringWriter(8 * KiB);
        PrintWriter out = new PrintWriter(writer);
        
        // 创建局部变量空间
        variableMap.newVariableSpace();
        
        // 覆盖原生的output
        variableMap.putValue("out", out);
        try {
            
            for (int i = 0; i < variableNames.size(); i++)
            // 传入变量值，供函数内获取调用
            {variableMap.putValue(variableNames.get(i), i >= args.size() ? null : args.get(i));}
            
            try {
                
                // 处理子集标签内容
                Iterator<Statement> iterator = childs.iterator();
                while (iterator.hasNext()) {
                    // 循环内容
                    Statement stmt = iterator.next();
                    stmt.execute(variableMap, out);
                }
            }
            catch (ReturnException e) {
                return e.getValue();
            }
            catch (Throwable e) {
                Throwable ex = e;
                do {
                    if (ex instanceof ReturnException) {
                        return ((ReturnException) ex).getValue();
                    }
                }
                while ((ex = ex.getCause()) != null);
                
                if (e instanceof StatementException) {throw e;}
                
                throw new StatementException(getPath(), getLineNo(), getBeginColumn(), this + " " + e.getMessage(), e);
                
            }
            
            return writer.toString();
        }
        finally { // 销毁局部变量空间
            out.close();
            try {
                writer.close();
            }
            catch (IOException e) {
            }
            variableMap.releaseVariableSpace();
        }
    }
    
    @Override
    public TagType getType() {
        return TagType.FUNCTION;
    }
    
    /**
     * 获取参数个数
     */
    public int getParameterCount() {
        return variableNames.size();
    }
    
    @Override
    public boolean parseTag(StatementParser parser) throws StatementException {
        StringBuilder value = new StringBuilder();
        char ch = 0, quote = 0, prvChar = 0;
        int expressionLine = this.getLineNo(), column = 0;
        boolean space = false;
        while (parser.hasNext()) {
            prvChar = value.length() == 0 ? 0 : ch;
            ch = parser.next();
            if (value.length() == 0 && Validates.isWhitespace(ch)) {
                space = true;
                continue;
            }
            
            value.append(ch);
            if (!space) {
                return false;// 只是以#include开头而已
            }
            
            if (functionName == null && _PARENTHESIS_LEFT == ch) {// 找到函数名
                
                if (value.length() == 1) {
                    throw new StatementException(this.getPath(),
                                                 this.getLineNo(),
                                                 this.getBeginColumn(),
                                                 StrUtils.formats("%s未找到函数名", getType()));
                }
                
                // 校验函数名
                functionName = StrUtils.trim(value.substring(0, value.length() - 1));
                if (!Validates.isJavaNameIdentifier(functionName)) {
                    throw new StatementException(this.getPath(),
                                                 parser.getLineNum(),
                                                 parser.getColumn() - 1,
                                                 StrUtils.formats("%s标签解析时无效函数名，请参见java命名规范。数据值:[%s]",
                                                                  getType(),
                                                                  functionName));
                }
                
                expressionLine = parser.getLineNum();
                column = parser.getColumn();
                value.setLength(0);
                quote = 0;
                value.append(ch);// 保留最后一个左括号
                continue;
            }
            
            // 解析数据值
            // 解析非字符串内的值数据操作符,避免意外结束，例如：<#var ss="/>" />
            if (quote == 0 && (ch == _DOUBLE_QUOTE_ || ch == _QUOTE_)) {// 记录 字符串开始
                quote = ch;
                continue;
            }
            
            // 判断字符串值
            if (quote != 0 && prvChar != _BACK_SLASH_ && ch == quote) {
                quote = 0;// 字符串结束
                continue;
            }
            
            // 判断标签是否结束
            if (!(quote == 0 && ch == _GATHER_)) {
                continue;// 非结束后符 ‘>’
            }
            
            // 字符串结束并且 标签开始订已完成
            this.setStatement(this.getStatement() + functionName + value);
            this.setCompleteTag();// 标签开始就绪
            value.setLength(value.length() - 1);
            String expression = StrUtils.trim(value.toString());
            
            if (expression.length() > 0) {// 长度大于0情况下，就必须由（）括起
                if (expression.charAt(0) != _PARENTHESIS_LEFT)// 检测左括号
                {
                    throw new StatementException(this.getPath(),
                                                 this.getLineNo(),
                                                 this.getBeginColumn(),
                                                 getType().getTagBegin() + " " + functionName + "函数缺少‘" + _PARENTHESIS_LEFT + "’");
                }
                if (expression.charAt(expression.length() - 1) != _PARENTHESIS_RIGHT)// 检测右括号
                {
                    throw new StatementException(this.getPath(),
                                                 this.getLineNo(),
                                                 this.getBeginColumn(),
                                                 getType().getTagBegin() + " " + functionName + "函数缺少‘" + _PARENTHESIS_RIGHT + "’");
                }
                
                expression = StrUtils.trim(expression,
                                           String.valueOf(_PARENTHESIS_LEFT),
                                           String.valueOf(_PARENTHESIS_RIGHT));
                String[] names = expression.split(",");
                for (int i = 0; i < names.length; i++) {
                    // 检测函数参数名
                    String name = names[i];
                    String variableName = StrUtils.trim(name);
                    boolean errName = false;
                    if (variableName.length() > 0 && !Validates.isJavaNameIdentifier(variableName)) {errName = true;}
                    
                    for (int j = 0; j < name.length(); j++) {
                        char c = name.charAt(j);
                        if (c == _LF_) {
                            expressionLine++;
                            column = 0;
                        }
                        
                        if (c != _LF_ && c != _CR_) {column++;}
                        
                        if (!Validates.isWhitespace(c) && errName) {break;}
                    }
                    
                    if (errName) {
                        throw new StatementException(this.getPath(),
                                                     expressionLine,
                                                     column,
                                                     StrUtils.formats("%s标签解析时无效函数参数名，请参见java命名规范。数据值:[%s]",
                                                                      getType(),
                                                                      variableName));
                    }
                    
                    // 保存函数参数名
                    if (variableName.length() > 0) {variableNames.add(variableName);}
                }
                
                break;
            }
        }
        
        if (functionName == null) {
            throw new StatementException(this.getPath(),
                                         this.getLineNo(),
                                         this.getBeginColumn(),
                                         "文档已结束。" + getType() + "标签未找函数名");
        }
        
        if (!this.isCompleteTag()) {
            throw new StatementException(this.getPath(),
                                         this.getLineNo(),
                                         this.getBeginColumn(),
                                         "文档已结束。" + getType() + "标签缺少‘>’");
        }
        
        this.setStatement(this.getStatement() + " " + functionName + "(" + Lists.toString(variableNames,
                                                                                          ", ") + ")" + this.getType()
                .getTagComplete());
        return true;
    }
    
}
